\chapter{Prototyp Implementierung}
\label{implementierung}
In diesem Kapitel wird die konkrete Umsetzung der Prototypen besprochen. Aktion und Reaktion auf Server- und Clientseite wird detailliert erklärt und relevante Code-Abschnitte der jeweiligen Prototypen werden dokumentiert.

\section{Entwicklungsumgebung}
\subsection{Hardware}
Für die Prototypentwicklung wurde ein MacBook Pro mit folgender Konfiguration benutzt:

\begin{itemize}
	\item  Model Name: MacBook Pro
	\item  Model Identifikator: MacBookPro8,1
	\item  Prozessor Name: Intel Core i5
	\item Prozessor Speed: 2.3 GHz
	\item Anzahl an Prozessoren: 1
	\item Anzahl an Prozessor Cores: 2
	\item L2 Cache (pro Core): 256 KB
	\item L3 Cache: 3 MB
	\item Arbeitsspeicher: 4 GB
\end{itemize}

\subsection{Software}
Folgende Software wurde zur Entwicklung der Prototypen benutzt:

\begin{itemize}
	\item OS X Darwin Kernel Version 10.7.3
	\item Eclipse Version: 3.6.2
	\item Konsole iTerm Build 0.10 zum Starten, Stoppen und Loggen von APE-Server und Streamhub-Server (für Cometd wurde Eclipse und dessen Konsole benutzt)
\end{itemize}





\section{Streamhub \& GWT}
\subsection{Server}
\subsubsection{Installation}
Die neuste Version von Streamhub kann unter der URL \url{http://www.stream-hub.com/download.html} heruntergeladen werden. Das heruntergeladene ZIP-Archiv wird in einen beliebigen Ordner extrahiert. Im Verzeichnis \url{/examples/lib/jar} befindet sich Streamhub-Server in Form einer Java-JAR-Datei (hier streamhub-2.2.10.jar). Es muss zudem sichergestellt werden, dass sich auf dem Rechner eine funktionierende Version von "`Java Runtime Environment'' (\url{http://java.com/en/download/index.jsp}) befindet. Die Serverinstallation ist somit bereits abgeschlossen.

\subsubsection{Konfiguration}
Streamhub-Server benötigt keine spezifische Serverkonfiguration.

\subsubsection{Implementierung}
Der untenstehende Server-Code wird in den folgenden Abschnitten erklärt:

\begin{lstlisting}[label=streamhub-server-impl,caption=Implementierung des Server-Codes] 
package ch.eiafr.tic.streamhub.server;

import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
import java.util.Map.Entry;
import ch.eiafr.tic.streamhub.client.Topics;
import com.streamhub.api.Client;
import com.streamhub.api.JsonPayload;
import com.streamhub.api.Payload;
import com.streamhub.api.PublishListener;
import com.streamhub.api.SubscriptionListener;
import com.streamhub.api.PushServer;
import com.streamhub.api.SubscriptionManager;
import com.streamhub.nio.NIOServer;

public class StreamhubServer implements SubscriptionListener, PublishListener {
  
  
  public static void main(String[] args) throws Exception {
    new StreamhubServer();
  }

  private PushServer server = new NIOServer(7979);
  private Timer timer;
  private int clock_interval = 1000;
  private int timer_offset = 0;
  private boolean isTimerStarted = false;

  public StreamhubServer() throws Exception {
    server = new NIOServer(7979); // Create a new server on port 7979
    server.start(); // Start the server

    // Set subscription and publish listeners
    SubscriptionManager subscriptionManager = server.getSubscriptionManager();
    subscriptionManager.addSubscriptionListener(this);
    subscriptionManager.addPublishListener(this);

    System.out.println("Comet server started at " +
    		            "http://localhost:7979/.");
    System.out.println("Press any key to stop...");
    System.in.read();
    server.stop();
  }

  @Override
  public void onUnSubscribe(String topic, Client client) {
    System.out.println("Client " + client.getUid()
        + " unsubscribed from topic " + topic);
  }

  @Override
  public void onSubscribe(String topic, Client client) {
    log("Client " + client.getUid() + " subscribed for topic " + topic);
  }

  @Override
  public void onMessageReceived(Client client, String topic, Payload payload) {
    JsonPayload message = new JsonPayload(topic); // Create new Json object
    Map<String, String> fields = payload.getFields(); // Get fields of the payload

    // Fill in the fields of the payload 
    // into our message json object
    for (Entry<String, String> entry : fields.entrySet()) {
      message.addField(entry.getKey(), entry.getValue());
    }

    // Send message to all clients of specific topic
    server.publish(topic, message);

    log("Server received message " + payload.toString() + " at: "
        + System.currentTimeMillis());

    if (topic.compareTo(Topics.TIMER_BUTTON_CLICKED) == 0) {
      toggleTimer();
    }
  }

  /**
   * Stops timer if it was started and start timer if it was stopped
   */
  public void toggleTimer() {
    if (isTimerStarted) {
      stopTimer();
    } else {
      startTimer();
    }
    isTimerStarted = !isTimerStarted;
  }

  /**
   * Starts the timer. Each second we will do a publish to all clients
   * subscribed to the topic Topics.TIMER_UPDATED.
   */
  private void startTimer() {
    timer = new Timer();
    timer.schedule(new TimerTask() {

      @Override
      public void run() {
        JsonPayload message = new JsonPayload(Topics.TIMER_UPDATED);
        long milliTime = System.currentTimeMillis();
        
        message.addField(
            "Message",
            "Timer tick on server at: " 
            + Long.toString(milliTime)
        );
        
        log("Timer tick on server at: " + milliTime);
        server.publish(Topics.TIMER_UPDATED, message);
      }
    }, timer_offset, clock_interval);
    System.out.println("Timer started");
  }

  /**
   * Simply stops the timer
   */
  private void stopTimer() {
    timer.cancel();
    System.out.println("Timer stoped");
  }

  /**
   * Prints out a message on the server console
   * 
   * @param logMessage
   */
  private void log(String logMessage) {
    System.out.println(logMessage);
  }
}
\end{lstlisting}

Die Methode \textit{main} kreiert eine neue Instanz der \textit{StreamhubServer} Klasse.

\begin{lstlisting}[label=streamhub-server-impl-main,caption=Main Methode] 
public static void main(String[] args) throws Exception {
    new StreamhubServer();
  }
\end{lstlisting}


Im Konstruktor wird mit \textit{server = new NIOServer(7979);} eine neue Instanz der \textit{NIOServer} Klasse angefertigt. Parameter \textit{7979} bedeutet, dass der Server auf dem Port 7979 verfügbar sein soll. Mit \textit{server.start();} wird anschliessend der Server gestartet. Nach Starten des Servers muss dem Server mitgeteilt werden welche Klasse für den Empfang von Client-Anmeldungen (Subscriptions) und Client-Nachrichten (Pushes) verantwortlich sein soll. In unserem Beispiel ist die Klasse \textit{StreamhubServer} dafür verantwortlich. Die Definition von Subscription- und Puplish-Listeners geschieht mit folgendem Code:

\begin{lstlisting}[label=streamhub-server-impl-define-listeners,caption=Definieren der Subscription- und Publishlisteners] 
    SubscriptionManager subscriptionManager = server.getSubscriptionManager();
    subscriptionManager.addSubscriptionListener(this);
    subscriptionManager.addPublishListener(this);
\end{lstlisting}

Wie besagt, ist die Klasse \textit{StreamhubServer} für Subscription- und Push-Listeners verantwortlich. Dies bedeutet, dass die Methoden \textit{onUnSubscribe()}, \textit{onSubscribe()} und \textit{onMessageReceived()} in dieser Klasse definiert werden müssen. Die Namen der drei Methoden sind selbsterklärend.

Sobald sich ein Client beim Server anmeldet, wird die Methode \textit{onSubscribe()} aufgerufen, welche auf der Serverkonsole den neu angemeldeten Client ausgibt. Beim Abmelden eines Clients wird die \textit{onUnSubscribe()} Methode aufgerufen, welche ebenfalls den abgemeldeten Client auf der Serverkonsole ausgibt.

\begin{lstlisting}[label=streamhub-server-impl-onsubscribe,caption=Methode onSubscribe] 
@Override
  public void onSubscribe(String topic, Client client) {
    log("Client " + client.getUid() + " subscribed for topic " + topic);
  }
\end{lstlisting}


\begin{lstlisting}[label=streamhub-server-impl-onunsubscribe,caption=Methode onUnSubscribe] 
  @Override
  public void onUnSubscribe(String topic, Client client) {
    System.out.println("Client " + client.getUid()
        + " unsubscribed from topic " + topic);
  }
\end{lstlisting}

Falls der Server von einem Client eine Nachricht erhält, wird die Methode \textit{onMessageReceived()} mit den Parametern \textit{Client client, String topic} und  \textit{Payload payload} aufgerufen. Parameter \textit{client} repräsentiert das Client-Objekt, welches die Nachricht an den Server geschickt hat. \textit{Topic} ist eine Zeichenkette, welche dem Server angibt an welchen Topic die eventuelle Antwort zurückgeschickt werden soll. Der Parameter \textit{payload} ist ein Json-Objekt, welches die vom Client gesendeten Daten enthält.

Falls in der Methode \textit{onMessageReceived()} eine Nachricht zurück an den Client geschickt werden soll, muss ein neues Json-Objekt vom Typ \textit{JsonPayload} erstellt werden. Es kann nicht direkt die vom Client geschickte \textit{payload} zurückgeschickt werden. Mit folgendem Java-Code wird ein Json-Objekt vom Typ \textit{JsonPayload} erstellt, welches den selben Inhalt wie der erhaltene \textit{payload} Parameter hat:

\begin{lstlisting}[language=Java,label=streamhub-json-return,caption=Zusammenfügen eines neuen Json-Objektes] 
// Create new Json message
JsonPayload message = new JsonPayload(topic); 

// Get fields of the payload
Map<String, String> fields = payload.getFields(); 

// Fill in the fields of the payload 
// into our message json object
for (Entry<String, String> entry : fields.entrySet()) {
    message.addField(entry.getKey(), entry.getValue());
}
\end{lstlisting}

Sobald das neue Json-Objekt fertiggestellt ist, können wir eine Antwort an alle Clients schicken, welche dem entsprechenden Topic angemeldet sind:

\begin{lstlisting}[language=Java,label=streamhub-json-send,caption=Senden einer Nachricht] 
server.publish(topic, message);
\end{lstlisting}

Laut Kapitel \ref{prototyp-spezifikation} ("`Prototyp Spezifikation''), muss auf Seite des Servers ein Timer gestartet werden, welcher jede Sekunde eine Nachricht an alle Clients schickt. Wir überprüfen dazu in der Methode \textit{onMessageReceived()}, ob der Client den Timer-Knopf betätigt hat. Falls dies der Fall ist, starten oder stoppen wir den Timer:

\begin{lstlisting}[language=Java,label=streamhub-toggle-timer,caption=Starten und stoppen des Timers] 
if (topic.compareTo(Topics.TIMER_BUTTON_CLICKED) == 0) {
      toggleTimer();
}
\end{lstlisting}

Die Methode \textit{toggleTimer()} überprüft den Wert des Attributes \textit{isTimerStarted}, um zu schauen ob der Timer bereits gestartet ist. Falls dies nicht der Fall ist, wird die Methode \textit{startTimer()} aufgerufen.

\begin{lstlisting}[language=Java,label=streamhub-toggle-timer3,caption=Ist der Timer bereits gestartet?] 
  public void toggleTimer() {
    if (isTimerStarted) {
      stopTimer();
    } else {
      startTimer();
    }
    isTimerStarted = !isTimerStarted;
  }
\end{lstlisting}


In der Methode \textit{startTimer()} wird wiederum ein neues Json-Objekt zusammengestellt, welches die aktuelle Server-Zeit beinhaltet. Somit kann der Client nach Erhalt der Nachricht vom Server, den Zeitunterschied der Übertragung berechnen. Folgender Code ist Teil der Methode \textit{startTimer()}:


\begin{lstlisting}[language=Java,label=streamhub-toggle-timer2,caption=Verschicken einer Nachricht im Sekundentakt] 
JsonPayload message = new JsonPayload(Topics.TIMER_UPDATED);
long milliTime = System.currentTimeMillis();

message.addField(
	"Message",
	"Timer tick on server at: " 
	+ Long.toString(milliTime)
);

log("Timer tick on server at: " + milliTime);
server.publish(Topics.TIMER_UPDATED, message);
\end{lstlisting}

Die implementierte Java-Klasse kann jetzt mit folgendem Befehl kompiliert werden:

\begin{lstlisting}[numbers=none,language=,label=stramhub-compile,caption=Kompilieren der Java-Server-Klasse] 
javac -cp streamhub-[aktuelle Version].jar StreamhubServer.java
\end{lstlisting}

Der Server ist nun bereit um gestartet zu werden:

\begin{lstlisting}[numbers=none,language=,label=stramhub-startup,caption=Starten des Streamhub-Servers] 
java -cp .:streamhub-[aktuelle Version].jar:log4j-[aktuelle Version].jar:json-[aktuelle Version].jar StreamhubServer
\end{lstlisting}

\subsection{Client}
\label{streamhub-client-installation}
\subsubsection{Installation}
Das bei \url{http://www.stream-hub.com/download.html} heruntergeladene ZIP-Archiv enthält im Ordner \url{/examples/lib/jar} die Dateien \textit{streamhub-gwt-adapter-1.0.4.jar} und \textit{streamhub-java-adapter-sdk-2.2.10.jar}. Es genügt diese Beiden JAR-Dateien in den Buildpath von Eclipse mit einzubinden, um Streamhub mit Eclipse benutzen zu können.

Für das GWT-Framework existiert ein Eclipse Plugin, mit welchem GWT-Applikationen direkt in Eclipse entwickelt werden können. Dieses Plugin wurde unter \url{http://code.google.com/webtoolkit/download.html} heruntergeladen und installiert. Auf die Installation und Konfiguration des GWT-Frameworks wird in dieser Dokumentation nicht weiter eingegangen.

Sobald das GWT-Plugin für Eclipse installiert ist, muss eine neue GWT-Webapplikation erstellt werden. Dies geschieht indem man in Eclipse das Menu \textit{File -\textgreater New -\textgreater Other} auswählt. Anschliessend ist unter dem Ordner "`Google'' der Eintrag "`Web Application Project'' auszuwählen.

\EPSFIGSCALE[0.4]{figures/gwt-install-1.jpg}{Erstellen einer Google Web Application}{gwt-install-1}

Im nächsten Schritt muss Projekt- und Paketname angegeben werden. In diesem Beispiel wurde als Projektname "`Streamhub-GWT'' und als Paketname "`ch.eiafr.tic'' angegeben. Die Checkbox "`Use Google App Engine'' kann entfernt werden. In diesem Beipiel wird dank der Checkbox "`Generate GWT project sample code'' bereits eine Einstiegsklasse, die den selben Namen wie das erstellte Projekt trägt erstellt.

\EPSFIGSCALE[0.4]{figures/gwt-install-2.jpg}{Projektname und Paketname angeben}{gwt-install-2}

Nach Klicken auf "`Finish'' wird eine neue GWT-Applikation erstellt. Die Beispielklassen \textit{GreetingService}, \textit{GreetingServiceAsync}, \textit{GreetingServiceImpl} und \textit{FieldVerifier} können gelöscht werden, sie werden in diesem Projekt nicht gebraucht. Der Code der Methode \textit{onModuleLoad()} in der Klasse \textit{Streamhub\_GWT} kann ebenfalls gelöscht werden (die Methode selber jedoch nicht). Somit ist das Eclipse-Projekt bereit um zur Implementierung überzugehen.

\subsubsection{Konfiguration}
Keine weiteren Konfigurationen sind erforderlich um Streamhub GWT Adapter zu benutzen.

\subsubsection{Implementierung}
Eine GWT-Applikation besitzt stets einen Einstiegspunkt. In unserem Beispiel ist der Einstiegspunkt die Methode \textit{onModuleLoad()}, in der Klasse \textit{Streamhub\_GWT} (Einstiegspunkt wurde dank der Option "`Generate GWT project sample code'' bei der Erstellung der Eclipse-Projektes erstellt). Folgender Code befindet sich in der Klasse \textit{Streamhub\_GWT}:
	


\begin{lstlisting}[label=streamhub-einstiegspunkt,caption=Klasse \textit{Streamhub\_GWT} mit Einstiegspunkt \textit{onModuleLoad()}]
package ch.eiafr.tic.streamhub.client;

import com.google.gwt.core.client.EntryPoint;
import com.streamhub.client.StreamHubGWTAdapter;

/**
 * Entry point classes define <code>onModuleLoad()</code>.
 */
public class Streamhub_GWT implements EntryPoint {
  public static StreamHubGWTAdapter streamhub = new StreamHubGWTAdapter();

  /**
   * This is the entry point method.
   */
  public void onModuleLoad() {
    Config.getInstance();
    MainView.getInstance(); (*@\label{main-view-getinstance}@*)
    subscribeToTopics(); (*@\label{streamhub-subscribeToTopics}@*)
  }

  private void subscribeToTopics() {
    streamhub.subscribe(
    	Channels.TIMER_BUTTON_CLICKED, 
    	new TimerButtonStateChangedListener()
    );
    streamhub.subscribe(
    	Channels.TIMER_UPDATED, 
    	new TimerUpdatedListener()
    );
  }
}
\end{lstlisting}

Wie im Kapitel \ref{design} besprochen, muss der Client sich mit dem Server verbinden, bevor er sich einem Channel anmelden kann. Dies geschieht im Konstruktor der \textit{Config} Klasse:

\begin{lstlisting}[label=streamhub-config-klasse,caption=Konfigurations Klasse]
private Config() {
	streamhub = new StreamHubGWTAdapter();
	streamhub.connect(CONNECTION_URL);
}
\end{lstlisting}

\textit{CONNECTION\_URL} besteht aus der Zeichenkette \textit{http://localhost:7979/streamhub}, wobei \textit{streamhub} einen von Streamhub vorgegebenen Channel ist, um sich mit dem Server zu verbinden.

Auf Zeile \ref{main-view-getinstance} des Listing \ref{streamhub-einstiegspunkt} wird eine neue Instanz der Klasse \textit{MainView} erstellt. Diese beinhaltet die Graphischen Elemente, die für die Prototyprealisation notwendig sind (Timer-Knopf und Textfeld). Auf den Code in \textit{MainView} wird nicht weiter eingegangen.

Auf Zeile \ref{streamhub-subscribeToTopics} des Listing \ref{streamhub-einstiegspunkt} wird die Methode \textit{subscribeToTopics()} aufgerufen. Diese meldet den Client zwei verschiedenen Channels an. Ein Channel wird benutzt, um über den aktuellen Knopfstatus informiert zu werden (Text des Knopfes "`Start Timer'' oder"`Stop Timer'') und der andere Channel brauchen wir, um vom Server die Zeitnachrichten zu erhalten.

\begin{lstlisting}[label=subscribeToTopics,caption=Client meldet sich zwei verschiedenen Channels an] 
private void subscribeToTopics() {
    streamhub.subscribe(
    	Channels.TIMER_BUTTON_CLICKED, 
    	new TimerButtonStateChangedListener()
    );
    streamhub.subscribe(
    	Channels.TIMER_UPDATED, 
    	new TimerUpdatedListener()
    );
}
\end{lstlisting}

\textit{Channels.TIMER\_BUTTON\_CLICKED} und \textit{Channels.TIMER\_UPDATED} sind Zeichenketten, welche die jeweiligen Channels identifizieren. \textit{TimerButtonStateChangedListener} und \textit{TimerUpdatedListener} sind die beiden im Kapitel Prototyp Design angesprochenen Listener-Klassen, welche für den Empfang der Nachrichten eines spezifischen Channels verantwortlich sind. Das Prinzip der beiden Listener ist genau das selbe. Deshalb untenstehend, nur der implementierte Code der \textit{TimerUpdatedListener} Klasse:

\begin{lstlisting}[label=TimerUpdatedListener,caption=Listener für den Empfang der Nachrichten des Channels \textit{Channels.TIMER\_UPDATED}] 
package ch.eiafr.tic.streamhub.client;

import com.google.gwt.json.client.JsonObject;
import com.streamhub.client.StreamHubGWTUpdateListener;

public class TimerUpdatedListener implements StreamHubGWTUpdateListener {

	@Override
	public void onUpdate(String topic, JsonObject update) {
			MainView mainView = MainView.getInstance();
			mainView.log("Client time when timer tick message arrived: " + System.currentTimeMillis());
			mainView.log(update.toString());
			mainView.log("-- -- -- -- -- -- -- -- -- -- ");
	}
}
\end{lstlisting}

Bei Erhalt einer Nachricht gibt der Listener die gewünschten Informationen im Browser aus, indem er die Methode \textit{log()} der Klasse \textit{MainView} benutzt. Die ausgeführte Methode \textit{onUpdate()} erhält den Parameter \textit{JsonObject update}, welcher die vom Server gesendeten Informationen enthält. Mit \textit{mainView.log(update.toString());} werden diese ebenfalls im Browser angezeigt.

Die im Kapitel \ref{prototyp-spezifikation} ("`Prototyp Spezifikation'') erläuterten Zeitmessungen können nun ausgeführt werden. Auf die Zeitmessungen wird im nächsten Kapitel, "`Prototyp Analyse'', eingegangen.

\newpage

\section{Ajax Push-Engine (APE)}
\subsection{Server}
Die untenstehenden Installations- und Konfigurationsschritte sind Teil der offiziellen Installationsanweisungen, die auf \url{http://www.ape-project.org/wiki/index.php/Advanced_APE_configuration} \cite{ape-project} zu finden sind.
\subsubsection{Installation}
Zur Installation des APE-Servers wurde die zurzeit aktuelle Version 1.0 von \url{http://www.ape-project.org/download/} heruntergeladen und auf der lokalen Festplatte entpackt. Eine vorkompilierte Version von APE-Server für OS X befindet sich im Ordner \url{/ape-server/APE_binary/bin} des Downloadpackets und kann direkt mit dem Befehl \textit{./aped} gestartet werden.

\subsubsection{Konfiguration}
APE-Server wurde konfiguriert, damit er in Zukunft auf einem eigenständigen Apache-Webserver funktionieren kann. Auf die Installation von Apache-Webserver wird in dieser Dokumentation nicht eingegangen. Damit der Prototyp via Browser mit einer bestimmten URL aufgerufen werden kann, muss dazu der entsprechende virtuelle Hosteintrag in der Datei \url{/etc/apache2/extra/httpd-vhosts.conf} gemacht werden. Im untenstehenden Beispiel wird der Prototypcode, der sich im Verzeichnis \url{/Users/yves/www/APE/}  befindet (\textit{DocumentRoot}), per URL \url{http://ape-test.local} zugänglich gemacht (\textit{ServerName}). Die beiden Einträge \textit{ServerAlias} bedeuten, dass auf  den Prototyp ebenfalls per Subdomain \textit{ape} und \textit{*.ape} zugegriffen werden kann (* = Platzhalter für eine beliebige Zeichenkette).

\begin{lstlisting}[numbers=none,label=vhost-ape,caption=Virtueller Hosteintrag des Apache Servers]
<VirtualHost *>
    ServerName ape-test.local
    ServerAlias ape.ape-test.local
    ServerAlias *.ape.ape-test.local
    DocumentRoot /Users/yves/www/APE/
</VirtualHost>	
\end{lstlisting}

In der Konfigurationsdatei \url{/ape-server/APE_binary/bin/ape.conf} muss anschliessend der Eintrag \textit{domain} angepasst werden. Dieser muss dem Eintrag \textit{ServerName} der Apache Virtual-Host Konfigurationsdatei entsprechen (siehe Listing \ref{vhost-ape}).

\newpage

\begin{lstlisting}[numbers=none,label=config-ape,caption=Konfigurationsdatei des APE-Servers. Der Eintrag"`domain'' muss angepasst werden]
Server {
    port = 6969
    daemon = no
    ip_listen = 0.0.0.0
    domain = ape-test.local
    rlimit_nofile = 10000
    pid_file = /var/run/aped.pid
}
\end{lstlisting}

Falls APE-Server unabhängig von Apache-Webserver installiert werden soll, kann in der Konfigurationsdatei \url{/ape-server/APE_binary/bin/ape.conf}, Eintrag \textit{port} auf \textit{80} und Eintrag \textit{ip\_listen} auf die entsprechende IP-Adresse des Servers gesetzt werden. Es ist dabei nicht zu vergessen, dass APE-Server keinen Webserver beinhaltet und ausschliesslich als Push-Engine genutzt werden kann. Wenn APE-Server also auf Port 80 gesetzt wird und unabhängig von einem Webserver auf einem Server installiert wird, muss zwingend ein Webserver (z.B. Apache) vorhanden sein, welcher die HTTP-Anfragen des Clients entgegen nimmt.

Falls APE-Server auf einem lokalen Rechner benutzt wird, der sich nicht in einer Produktionsumgebung befindet, müssen  folgende Einträge in die Datei \url{/etc/hosts} gemacht werden:

\begin{lstlisting}[numbers=none,label=etc-hosts,caption=Eintrag der lokalen Host Datei]
127.0.0.1   ape-test.local
127.0.0.1   ape.ape-test.local
\end{lstlisting}

Falls es sich bei der APE-Installation um eine definitive Produktionsumgebung handelt, muss anstelle des Eintrages in \url{/etc/hosts}, der entsprechende DNS-Eintrag gemacht werden.

\begin{lstlisting}[numbers=none,label=bind9,caption=Beispiel eines DNS Eintrages (bind9)]
ape     IN    A        x.x.x.x  ; The IP address of your APE server
*.ape   IN    CNAME    ape
\end{lstlisting}

Die Konfiguration des APE-Servers ist damit abgeschlossen.

\subsubsection{Implementierung}
\label{ape-server-impl}
Wie im Kapitel \ref{design} beschrieben, werden auf Seite des Servers sogenannte Raws an den Client geschickt. Diese können entweder in C oder JavaScript implementiert werden. In dieser Dokumentation wird ausschliesslich auf die Implementierung mit JavaScript eingegangen.\\
Die sogenannten JavaScript-Module befinden sich im Ordner \url{/ape-server/APE_binary/scripts} der Serverinstallation und werden beim Starten des Servers in die Programmiersprache C umgewandelt. Auf die Implementierung der JavaScript-C-Umwandlung wird in dieser Dokumentation nicht eingegangen.

Um die im Kapitel "`Prototyp Spezifikation'' definierten Ziele zu erfüllen, wurde im Verzeichnis \url{/ape-server/APE_binary/scripts} die Datei \textit{timer.js} erstellt, in der die Raws implementiert sind. In den Folgenden Abschnitten wird der untenstehende Code erklärt.

\begin{lstlisting}[language=JavaScript,label=timer-js,caption=Server-Modul timer.js] 
var timeoutID;
var isTimerStarted = false;

// Register a new command "toggleTimer"
Ape.registerCmd('toggleTimer', true, function(params, cmd) { (*@\label{timer-1}@*)

    // Get the channel where data came from
    var channel = Ape.getChannelByPubid(params.pipe);
    if (channel) {

        // Send data to the channel, with server time
        // and button text as parameters
        if (params.clientTime) {
            var clientTime = params.clientTime;
            var serverTime = new Date().getTime();
            var buttonText;
            
            Ape.log('Client time when timer button was pressed: ' + clientTime);
            Ape.log('Server time when command "toggleTimer" received: ' + serverTime.toString());
            logTimeDifference(clientTime, serverTime);   
            
            if (!isTimerStarted) {
                buttonText = 'Stop timer';
                timeoutID = Ape.setInterval(timerTick, 1000, channel);
            } else {              
                Ape.clearInterval(timeoutID);
                buttonText = 'Start timer';
            }
            
            isTimerStarted = !isTimerStarted;
            channel.pipe.sendRaw('changeTimerButtonText', {'text': buttonText});
        }
    } else {
        return ['401', 'UNKNOWN_CHANNEL'];
    }
});

function logTimeDifference(clientTime, serverTime) {
    var timeDifference = serverTime - clientTime;
    Ape.log('Time difference between timer button click on client side and reception of command "toggleTimer" on server side: ' + timeDifference.toString() + ' ms\n');
}


function timerTick(channel) {
    var serverTime = new Date().getTime();
    channel.pipe.sendRaw('timerTick', {'time': serverTime.toString()});
    Ape.log('Timer tick: ' + serverTime.toString() );
}
\end{lstlisting}

Damit der Server auf bestimmte Commands reagieren kann, werden diese im Server-Modul registriert. Dies geschieht mit der Methode \textit{registerCmd()}.

\begin{lstlisting}[numbers=none,label=registerCmd,caption=,language=] 
string registerCmd ( string name, boolean require_sessid, function fn ) 
\end{lstlisting}

\textit{string name} ist der Identifikator der Command, \textit{boolean require\_sessid} gibt an, ob der Client eingeloggt sein muss um die Command auszuführen und \textit{function fn} ist die Funktion, welche aufgerufen wird sobald der Server die Command vom Client erhalten hat. Die aufgerufene Funktion hat zwei Parameter:

\begin{enumerate}
	\item params - (object) Liste der Parameter die vom Client gesendet werden
	\item info - (object) Enthält Informationen über den Client:
	
		\begin{itemize}
			\item host (string): Der im HTTP Header enthaltene Hostname
			\item client (socket): Socket Objekt des Clients
			\item chl (number): Die Challenge Nummer (vergleichbar mit TCP Sequenznummer)
			\item ip (string): IP Adresse des Clients
			\item user (user): Userobjekt (nur wenn eingeloggt)
			\item subuser (subuser): Userobjekt (nur wenn eingeloggt)
			\item http (array): HTTP Headers
		\end{itemize}
\end{enumerate}

Mit \textit{Ape.getChannelByPubid(params.pipe)} kann der Server herausfinden über welchen Channel er die Antwort auf die Command zurückschicken soll.\\

\begin{lstlisting}[numbers=none,label=getChannelByPubid,caption=,language=] 
var channel = Ape.getChannelByPubid(params.pipe);
\end{lstlisting}

Mit folgendem Codeabschnitt gibt der Server die Client-Zeit des Knopfdruckes, die Server-Zeit nach Erhalt der Command und den Zeitunterschied zwischen Versenden und Erhalten der Command in der Konsole aus:

\begin{lstlisting}[label=timediff,caption={Entgegennehmen der Parameter und Ausgabe von Client-Zeit, Server-Zeit und Zeitunterschied}] 
if (params.clientTime) {
            var clientTime = params.clientTime;
            var serverTime = new Date().getTime();
            var buttonText;
            
            Ape.log('Client time when timer button was pressed: ' + clientTime);
            Ape.log('Server time when command "toggleTimer" received: ' + serverTime.toString());
            logTimeDifference(clientTime, serverTime);   
...
}

function logTimeDifference(clientTime, serverTime) {
    var timeDifference = serverTime - clientTime;
    Ape.log('Time difference between timer button click on client side and reception of command "toggleTimer" on server side: ' + timeDifference.toString() + ' ms\n');
}
\end{lstlisting}

Der Server reagiert auf die Command \textit{toggleTimer} mit zwei verschiedenen Arten von Raws. Die Raw \textit{changeTimerButtonText} wird geschickt um die Clients zu informieren, dass der Text des Timer-Knopfes von "`Start timer'' nach "`Stop timer'' (oder umgekehrt) geändert werden soll. Eine zweite Raw \textit{timerTick} wird anschliessend jede Sekunde an die Clients geschickt. Die Raw \textit{timerTick} beinhaltet jeweils die exakte Server-Zeit zum Zeitpunkt des Abschickens der Raw (siehe Kapitel \ref{design}, Prototyp Design). 

Die Methode \textit{integer setInterval (function fn, integer delay, [string param1, param2, ...])} ermöglicht es, eine Funktion repetitiv aufzurufen. Der Parameter \textit{integer delay} bestimmt die Zeit zwischen den jeweiligen Aufrufen. Mit der Funktion \textit{string pipe.sendRaw (string name, object data, [object options])} wird vom Server die bereits im Kapitel "`Prototyp Design'' erklärte Raw an einen Channel geschickt.

\begin{lstlisting}[label=change-text-and-start-timer,caption=Verschicken des neuen Knopftextes und Starten des Timers] 
...
if (!isTimerStarted) {
                buttonText = 'Stop timer';
                timeoutID = Ape.setInterval(timerTick, 1000, channel);
            } else {              
                Ape.clearInterval(timeoutID);
                buttonText = 'Start timer';
            }
            
            isTimerStarted = !isTimerStarted;
            channel.pipe.sendRaw('changeTimerButtonText', {'text': buttonText});
        }
    } else {
        return ['401', 'UNKNOWN_CHANNEL'];
    }
...


function timerTick(channel) {
    var serverTime = new Date().getTime();
    channel.pipe.sendRaw('timerTick', {'time': serverTime.toString()});
    Ape.log('Timer tick: ' + serverTime.toString() );
}
\end{lstlisting}

Die Serverimplementation ist somit abgeschlossen.

\subsection{Client}
\subsubsection{Installation}
Im Downloadpacket, welches von \url{http://www.ape-project.org/download} heruntergeladen wurde, befindet sich im Root-Verzeichnis der Ordner \textit{ape-jsf} (jsf für JavaScript-Framework). Dieser wird in den im Listing \ref{vhost-ape} angegebenen Pfad des \textit{DocumentRoot} kopiert (hier \textit{/Users/yves/www/APE/}). Es sind keine weiteren Installationen auf Seite des Clients nötig.

\subsubsection{Konfiguration}
Bevor der Prototyp via URL \url{http://ape-test.local}, oder in unserem Fall \url{http://ape-test.local/ape-jsf} aufgerufen werden kann, muss in der globalen Konfigurationsdatei \url{/ape-jsf/Demos/config.js} \textit{baseUrl}, \textit{domain} und \textit{server} angepasst werden:

\begin{lstlisting}[language=,numbers=none,label=js-config-ape,caption=APE JavaScript Konfigurationsdatei]
/***
 * APE JSF Setup
 */
APE.Config.baseUrl = 'http://ape-test.local/ape-jsf'; //APE JSF 
APE.Config.domain = 'ape-test.local'; // same as domain in aped.conf
APE.Config.server = 'ape.ape-test.local:6969'; //APE server URL

(function(){
	for (var i = 0; i < arguments.length; i++)
		APE.Config.scripts.push(APE.Config.baseUrl + '/Source/' + arguments[i] + '.js');
})(
    'mootools-core', 
    'Core/APE', 
    'Core/Events', 
    'Core/Core', 
    'Pipe/Pipe', 
    'Pipe/PipeProxy', 
    'Pipe/PipeMulti', 
    'Pipe/PipeSingle', 
    'Request/Request',
    'Request/Request.Stack', 
    'Request/Request.CycledStack', 
    'Transport/Transport.longPolling',
    'Transport/Transport.SSE', 
    'Transport/Transport.XHRStreaming', 
    'Transport/Transport.JsonP', 
    'Core/Utility', 'Core/Json');
\end{lstlisting}

\subsubsection{Implementierung}
Für die Implementierung auf Clientseite wurde im Verzeichnis \url{/ape-jsf} die Datei \textit{index.html} erstellt, welche automatisch nach Aufruf der URL \url{http://ape-test.local/ape-jsf} vom Browser geladen und interpretiert wird. Die Datei \textit{index.html} inkludiert die JavaScript-Datei \url{./inc/timer.js}, welche die APE-Implementation beinhaltet.

Folgender HTML-Code befindet sich in der Datei \textit{index.html}:

\begin{lstlisting}[language=html,label=ape-html,caption=Startseite der implementierten APE Applikation]
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" dir="ltr" lang="en">
  <head>  
    <title>Timer</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <script type="text/javaScript" src="./Source/jquery-1.5.2.min.js"></script>
    <script type="text/javaScript" src="./Clients/JavaScript.js"></script>
    <script type="text/javaScript" src="./Demos/config.js"></script>
  </head>
  <body>
    <input id="timerButton" type="submit" value="Start timer" />	
    <div id="timerLog"></div>
    <script type="text/javaScript" src="./inc/timer.js"></script>
  </body>
</html>
\end{lstlisting}

Die inkludierte JavaScript-Datei beinhaltet folgenden Code, welcher in den untenstehenden Abschnitten erklärt wird:


\begin{lstlisting}[language=JavaScript,label=ape-javascript-client,caption=JavaScript Code der implementierten APE Applikation]
// Load APE Client and auto join timerChannel
var client = new APE.Client();
client.load({
	'channel' : 'timerChannel'
});

// When client is loaded, connect to APE-Server with a random name
client.addEvent('load', function() {
	client.core.start({
		'name' : new Date().getTime().toString()
	});
});

// Catch multipipeCreate event
client.addEvent('multiPipeCreate', function(pipe) {
	// Attach click event to all div with colorChanger class
	$('#timerButton').click(function() {
		// 4) Send a command "startTimer" on the pipe (channel) with client time
		pipe.request.send('toggleTimer', {
			'clientTime' : new Date().getTime().toString()
		});
	});
});

// Intercept changeTimerButtonText raw
client.onRaw('changeTimerButtonText', function(params) {
	$('#timerButton').val(params.data.text);
});

// Intercept timerTick raw
client.onRaw('timerTick', function(params) {
	var clientTime = new Date().getTime();
	var serverTime = params.data.time;
	var timeDifference = clientTime - serverTime;
	var clientTimeDiv = document.createElement('div');
	var serverTimeDiv = document.createElement('div');
	var timeDifferenceDiv = document.createElement('div');
	var br = document.createElement('br');
	
	serverTimeDiv.appendChild(document.createTextNode('Server time: ' + serverTime.toString()));
	clientTimeDiv.appendChild(document.createTextNode('Client time: ' + clientTime.toString()));
	timeDifferenceDiv.appendChild(document.createTextNode('Time difference: ' + timeDifference.toString()));
	
	$('#timerLog').prepend(timeDifferenceDiv);
	$('#timerLog').prepend(clientTimeDiv);
	$('#timerLog').prepend(serverTimeDiv);
	$('#timerLog').prepend(br);
});
\end{lstlisting}

Wie im Kapitel \ref{design} ("`Prototyp Design'') besprochen, muss sich der Client einem sogenannten Channel anmelden bevor er mit dem Server kommunizieren kann. Dies geschieht mit:

\begin{lstlisting}[language=JavaScript,label=ape-javascript-client-channel-join,caption=Client meldet sich dem Channel"`timerChannel'' an]
// Load APE Client and auto join timerChannel
var client = new APE.Client();
client.load({
	'channel' : 'timerChannel'
});
\end{lstlisting}

Sobald die Instanz des Clients geladen ist und sich der Client dem Channel "`timerChannel'' angemeldet hat, wird vom Server ein sogenannter "`Event'' geschickt (auf APE Events wird in dieser Dokumentation nicht weiter eingegangen). Der geschickte Event trägt den Namen \textit{load} um zu kennzeichnen, dass der Client erfolgreich geladen ist. Der Client reagiert auf den \textit{load} Event, indem er sich mit dem Server verbindet. Dies geschieht mit folgendem Code:

\begin{lstlisting}[language=JavaScript,label=ape-javascript-client-load,caption=Client meldet sich beim Server an]
// When client is loaded, connect to APE-Server with a random name
client.addEvent('load', function() {
	client.core.start({
		'name' : new Date().getTime().toString()
	});
});
\end{lstlisting}

Der Server antwortet wiederum auf die obenstehende Verbindungsanfrage des Clients, indem er den Event \textit{multiPipeCreate} zurückschickt. Zu diesem Zeitpunkt steht die Verbindung zwischen Server und Client. Der Client reagiert nun mit folgendem Code auf den \textit{multiPipeCreate} Event:

\begin{lstlisting}[language=JavaScript,label=ape-javascript-client-multipipecreate,caption=Registrieren des onClick Handlers]
// Catch multipipeCreate event
client.addEvent('multiPipeCreate', function(pipe) {
	// Attach click event to timer button
	$('#timerButton').click(function() {
		// 4) Send a command "toggleTimer" on the pipe (channel) with client time
		pipe.request.send('toggleTimer', { (*@\label{request-send}@*)
			'clientTime' : new Date().getTime().toString()
		});
	});
});
\end{lstlisting}

Das inkludierte JavaScript Framework jQuery ermöglicht es mit der Syntax \textit{\textdollar ('\#timerButton')} den JavaScript Code \textit{document.getElementById('timerButton')} abzukürzen. D.h. mit \textit{\textdollar ('\#timerButton').click(function())} wird ein \textit{onClick} Listener auf dem HTML-Element mit der id \textit{timerButton} registriert. Im HTML-Code ist der HTML-Knopf

\begin{lstlisting}[language=JavaScript,label=ape-javascript-client-timerButton,caption=]
<input id="timerButton" type="submit" value="Start timer" />
\end{lstlisting}

wiederzufinden, welcher die id \textit{timerButton} trägt. Sobald der HTML-Knopf vom Benutzer betätigt wird, schickt der Client die Command \textit{toggleTimer} an den Server (siehe Zeile \ref{request-send}, Listing \ref{ape-javascript-client-multipipecreate}). Mit dem JavaScript-Code \textit{new Date().getTime().toString()} wird die aktuelle Millisekunde auf Seite des Clients in das Json-Objekt gespeichert.

Nach Schicken der Command \textit{toggleTimer}, schickt der Server die Raws \textit{changeTimerButtonText} und \textit{timerTick} zurück (siehe Kapitel \ref{ape-server-impl}). Der Client kann folgendermassen auf die vom Server geschickten Raws reagieren:

\begin{lstlisting}[language=JavaScript,label=changeTimerButtonText,caption=Client ändert den Text des Knopfes sobald er die Raw \textit{changeTimerButtonText} erhalten hat] 
// Intercept changeTimerButtonText raw
client.onRaw('changeTimerButtonText', function(params) {
	$('#timerButton').val(params.data.text);
});
\end{lstlisting}


\begin{lstlisting}[language=JavaScript,label=timerTick,caption=Client reagiert auf \textit{timerTick} Raw] 
// Intercept timerTick raw
client.onRaw('timerTick', function(params) {
	var clientTime = new Date().getTime();
	var serverTime = params.data.time;
	var timeDifference = clientTime - serverTime;
	var clientTimeDiv = document.createElement('div');
	var serverTimeDiv = document.createElement('div');
	var timeDifferenceDiv = document.createElement('div');
	var br = document.createElement('br');
	
	serverTimeDiv.appendChild(document.createTextNode('Server time: ' + serverTime.toString()));
	clientTimeDiv.appendChild(document.createTextNode('Client time: ' + clientTime.toString()));
	timeDifferenceDiv.appendChild(document.createTextNode('Time difference: ' + timeDifference.toString()));
	
	$('#timerLog').prepend(timeDifferenceDiv);
	$('#timerLog').prepend(clientTimeDiv);
	$('#timerLog').prepend(serverTimeDiv);
	$('#timerLog').prepend(br);
});
\end{lstlisting}

Der Client reagiert auf die vom Server geschickte Raw \textit{timerTick}, indem er die in der Raw enthaltenen Daten im Browser anzeigt. Die im Kapitel "`Prototyp Spezifikation'' erläuterten Zeitmessungen können nun ausgeführt werden. Auf die Zeitmessungen wird im nächsten Kapitel"`Prototyp Analyse'' eingegangen.

\newpage

\section{Gwt-comet}
\subsection{Server}
\subsubsection{Installation}
Das GWT-Plugin für Eclipse beinhaltet bereits eine funktionierende Instanz des Jetty-Servers, welche automatisch gestartet wird, sobald der Run-Knopf von Eclipse betätigt wird. Sobald das GWT-Projekt fertiggestellt ist, wird dieses auf einem eigenständigen Jetty-Server installiert (unabhängig von Eclipse). Die eigenständige Installation wird in diesem Projekt benutzt, um die im Kapitel \ref{design} ("`Prototyp Design'') definierten Zeitmessungen durchzuführen.

Folgende Abschnitte beschreiben die Installation eines Jetty-Servers.

Jetty kann auf \url{http://docs.codehaus.org/display/JETTY/Downloading+Jetty} heruntergeladen werden. Für dieses Projekt wurde die Version \textit{jetty-hightide-7.4.0} benutzt. Das heruntergeladene Archiv kann in einen beliebigen Ordner der Betriebssystems entpackt werden. Die Installation von Jetty ist somit bereits abgeschlossen. Im entpackten Ordner befindet sich die JAR-Datei \textit{start.jar}. Der Server kann mit dem Befehl \textit{java -jar start.jar} gestartet werden.

\subsubsection{Konfiguration}
Damit Jetty beim Aufstarten die gewünschten Servlets (siehe Kapitel \ref{gwt-comet}, "`Datenaustausch von Gwt-comet'') zur Verfügung stellen kann, muss die Datei \textit{web.xml}, welche sich im Verzeichnis \url{/war/WEB-INF} des Eclipse-Projektes befindet, konfiguriert werden. Untenstehend den Inhalt von \textit{web.xml}:

\begin{lstlisting}[language=,label=gwt-comet-webxml,caption=Jetty-Serverkonfiguration]
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE web-app
    PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
    "http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>
	<!-- the RPC service called by the client -->
	<servlet>
		<servlet-name>messagingService</servlet-name>
		<servlet-class>ch.eiafr.tic.server.MessagingServiceImpl</servlet-class>
	</servlet>

	<!-- the comet servlet for streaming messages to the client -->
	<servlet>
    <servlet-name>messagingComet</servlet-name>
    <servlet-class>net.zschech.gwt.comet.server.CometServlet</servlet-class>
  </servlet>
  
  <!-- Servlets -->
	<servlet-mapping>
		<servlet-name>messagingService</servlet-name>
		<url-pattern>/comet_gwt/messaging</url-pattern>
	</servlet-mapping>

	<servlet-mapping>
		<servlet-name>messagingComet</servlet-name>
		<url-pattern>/comet_gwt/comet</url-pattern>
	</servlet-mapping>

	<!-- Default page to serve -->
	<welcome-file-list>
		<welcome-file>Comet_gwt.html</welcome-file>
	</welcome-file-list>
</web-app>
\end{lstlisting}

Auf die Bedeutung der einzelnen Einträge wird im Rahmen dieser Dokumentation nicht eingegangen. Kurz zusammengefasst, werden zwei verschiedene Servlets geladen. Eine ausführliche Dokumentation über \textit{web.xml} ist auf \url{http://www.caucho.com/resin-3.0/config/webapp.xtp} \cite{web-xml} zu finden.

\subsubsection{Implementierung}
Wie im Kapitel \ref{design} beschrieben, sind die öffentlichen Methoden des Servers mit einem Interface definiert. Für die Implementierung des Gwt-comet-Prototypes wurden drei Methoden in diesem Interface definiert.

\begin{lstlisting}[label=gwt-comet-server-interface,caption=Mit diesem Interface werden dem Client die öffentlichen Methoden des Servers bekannt gegeben]
package ch.eiafr.tic.client;

import com.google.gwt.user.client.rpc.RemoteService;
import com.google.gwt.user.client.rpc.RemoteServiceRelativePath;

@RemoteServiceRelativePath("messaging")
public interface MessagingService extends RemoteService {
  
  /**
   * Login and setup a CometSession on the chat server.
   * 
   * @param username
   * @throws LoginException 
   * @throws ChatException
   */
  public void login(String username) throws LoginException;
  
  /**
   * Logout and destroy the CometSession on the chat server.
   * 
   * @param username
   * @throws ChatException
   */
  public void logout(String username) throws LogoutException;
  
  
  /**
   * Send a message to all users on the message server.
   * 
   * @param message
   * @throws ChatException
   */
  public void toggleTimer(Message message) throws MessageException;
}
\end{lstlisting}

Das Interface definiert die Methoden \textit{login()}, \textit{logout()} und \textit{toggleTimer()}. \textit{login()} und \textit{logout()} werden vom Client benutzt, um sich beim Server an- bzw. abzumelden. Die Methode \textit{toggleTimer()} startet oder stoppt den Timer des Servers.

Die im Interface definierten Methoden müssen nun implementiert werden. Der untenstehende Code wird in den darauf folgenden Abschnitten diskutiert:

\begin{lstlisting}[label=gwt-comet-server-impl,caption=Implementierung der Serverlogik]
package ch.eiafr.tic.server;

import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import javax.servlet.http.HttpSession;
import net.zschech.gwt.comet.server.CometServlet;
import net.zschech.gwt.comet.server.CometSession;
import ch.eiafr.tic.client.LoginException;
import ch.eiafr.tic.client.LogoutException;
import ch.eiafr.tic.client.Message;
import ch.eiafr.tic.client.MessagingService;
import ch.eiafr.tic.client.MessageException;

import com.google.gwt.user.server.rpc.RemoteServiceServlet;

public class MessagingServiceImpl extends RemoteServiceServlet implements
    MessagingService {

  // A mapping of user names to CometSessions used for routing messages.
  private ConcurrentMap<String, CometSession> users = new ConcurrentHashMap<String, CometSession>();
  private Timer timer;
  private boolean isTimerStarted = false;
  private final String TIMER_BUTTON_START = "Start Timer";
  private final String TIMER_BUTTON_STOP = "Stop Timer";

  @Override
  public void toggleTimer(Message message) throws MessageException {
    long serverTime = System.currentTimeMillis();
    long clientTime = message.getTime();
    long timeDifference = serverTime - clientTime;

    System.out.println("Server received message at: " + serverTime);
    System.out.println("Client time: " + clientTime);
    System.out.println("Time difference: " + timeDifference);
    System.out.println();

    // check if there is a HTTP session setup.
    HttpSession httpSession = getThreadLocalRequest().getSession(false);
    if (httpSession == null) {
      throw new MessageException("not logged in: no http session");
    }

    // get the user name for the HTTP session.
    String username = (String) httpSession.getAttribute("username");
    if (username == null) {
      throw new MessageException("not logged in: no http session username");
    }

    // Create message to send to clients
    long newServerTime = System.currentTimeMillis();
    Message serverMessage = new Message(newServerTime);

    // Indicates that we want to change button text on client side
    serverMessage.setToggleButtonText(true);

    if (isTimerStarted) {
      message.setMessage(TIMER_BUTTON_START);
      stopTimer();
    } else {
      message.setMessage(TIMER_BUTTON_STOP);
      startTimer();
    }
    isTimerStarted = !isTimerStarted;
    sendMessage(message);
  }

  /**
   * Sends a message to all registered clients
   */
  public void sendMessage(Message message) {
    for (Map.Entry<String, CometSession> entry : users.entrySet()) {
      entry.getValue().enqueue(message);
    }
  }

  @Override
  public void login(String username) throws LoginException {
    // Get or create the HTTP session for the browser
    HttpSession httpSession = getThreadLocalRequest().getSession();
    // Get or create the Comet session for the browser
    CometSession cometSession = CometServlet.getCometSession(httpSession);
    // Remember the user name for the
    httpSession.setAttribute("username", username);

    // setup the mapping of user names to CometSessions
    if (users.putIfAbsent(username, cometSession) != null) {
      // some one else has already logged in with this user name
      httpSession.invalidate();
      throw new LoginException("User: " + username + " already logged in");
    }
  }

  @Override
  public void logout(String username) throws LogoutException {
    // check if there is a HTTP session setup.
    HttpSession httpSession = getThreadLocalRequest().getSession(false);
    if (httpSession == null) {
      throw new LogoutException("User: " + username
          + " is not logged in: no http session");
    }

    // check if there is a Comet session setup.
    // In a larger application the HTTP session
    // may have been setup via other means.
    CometSession cometSession = CometServlet
        .getCometSession(httpSession, false);
    if (cometSession == null) {
      throw new LogoutException("User: " + username
          + " is not logged in: no comet session");
    }

    // check the user name parameter
    // matches the HTTP sessions user name
    if (!username.equals(httpSession.getAttribute("username"))) {
      throw new LogoutException("User: " + username
          + " is not logged in on this session");
    }

    // remove the mapping of user name to CometSession
    users.remove(username, cometSession);
    httpSession.invalidate();
  }

  private void startTimer() {
    timer = new Timer();
    timer.schedule(new TimerTask() {

      @Override
      public void run() {
        final Message message = new Message("Timer tick: ", System
            .currentTimeMillis());
        sendMessage(message);
      }
    }, 0, 1000);
  }

  private void stopTimer() {
    timer.cancel();
  }
}

\end{lstlisting}

Die auf dem Server registrierten Benutzer werden in einer \textit{ConcurrentHashMap} gespeichert, welche als Attribut der Serverklasse definiert ist. Die \textit{ConcurrentHashMap} speichert die Benutzer wie in einer zweidimensionalen Tabelle ab, wobei die erste Kolonne jeweils dem Benutzernamen, und die zweite Kolonne einer eindeutigen Zeichenkette entspricht. Ein einzelner Eintrag (Reihe) dieser "`Tabelle'' identifiziert ein Benutzer auf dem Server. 

\begin{lstlisting}[numbers=none,label=,caption=]
private ConcurrentMap<String, CometSession> users = 
                                     new ConcurrentHashMap<String, CometSession>();
\end{lstlisting}

Sobald ein Benutzer die Methode \textit{login()} aufruft, überprüft diese mittels \textit{ConcurrentHashMap}, ob dieser Benutzer bereits auf dem Server registriert ist. Falls dieser bereits registriert ist, meldet dies der Server in Form einer Fehlermeldung. Bei der Methode \textit{logout()} wird der Benutzer aus der \textit{ConcurrentHashMap} gelöscht. Falls dieser nicht existieren sollte, gibt der Server ebenfalls eine Fehlermeldung aus.

\begin{lstlisting}[label=gwt-comet-server-login,caption=Ausschnitt aus Methode \textit{login()}. Benutzer wird der \textit{ConcurrentHashMap} hinzugefügt]
// setup the mapping of user names to CometSessions
if (users.putIfAbsent(username, cometSession) != null) {
    // some one else has already logged in with this user name
    httpSession.invalidate();
    throw new LoginException("User: " + username + " already logged in");
}
\end{lstlisting}

Wie bereits erläutert, startet die Methode \textit{toggleTimer()} den Server-Timer, welcher jede Sekunde eine Nachricht an alle angemeldeten Clients schickt. Das Prinzip des Starten und Stoppen des Timers ist das selbe wie bereits bei der Implementierung des Streamhub-Servers gesehen und wird deshalb nicht weiter besprochen. Für die Realisierung der definierten Ziele im Kapitel "`Prototyp Spezifikation'' werden zwei Arten von Meldungen an die Clients geschickt. Die eine Meldung signalisiert den Clients, dass ein anderer Client den Timer-Knopf betätigt hat, und die andere Meldung wird vom Timer jede Sekunde verschickt, um die aktuelle Server-Zeit mitzuteilen. Der wesentliche Unterschied zwischen Streamhub und Gwt-comet ist, dass bei Gwt-comet nur ein einziger Listener auf Seite des Clients für den Empfang der Servermeldungen registriert wird. Damit also zwischen den zwei Arten von Meldungen unterschieden werden kann, müssen entweder zwei verschiedene Objekt-Typen erstellt werden (z.B. \textit{TimerMessage} und \textit{ButtonChangeMessage}), oder es wird immer der selbe Objekt-Typ (z.B. \textit{Message}) gesendet, wobei innerhalb des Objektes \textit{Message} ein Attribut definiert wird, welches signalisiert ob auf Clientseite der Knopftext geändert werden muss. Bei der Implementierung des Gwt-comet-Prototypen wurde die zweite Variante gewählt.

Die Objekte, die vom Server aus gesendet werden, haben also den Typ \textit{Message}. Ein Objekt \textit{Message} besitzt drei Attribute: \textit{String message}, \textit{long time} und \textit{boolean toggleButtonText}.

\begin{lstlisting}[numbers=none,label=,caption=]
private String message;
private long time;
private boolean toggleButtonText = false;
\end{lstlisting}

In der Methode \textit{toggleTimer()} wird eine solche \textit{Message} als Parameter entgegengenommen. Somit kann die Client-Zeit, welche im Objekt gespeichert ist, ausgelesen werden.

\begin{lstlisting}[label=gwt-comet-auslesen-zeit,caption=Auslesen der Client-Zeit und Berechnung des Zeitunterschiedes. Die Resultate werden in der Serverkonsole ausgegeben]
@Override
public void toggleTimer(Message message) throws MessageException {
long serverTime = System.currentTimeMillis();
long clientTime = message.getTime();
long timeDifference = serverTime - clientTime;

System.out.println("Server received message at: " + serverTime);
System.out.println("Client time: " + clientTime);
System.out.println("Time difference: " + timeDifference);
System.out.println();

...

\end{lstlisting}

Die Implementierung des Gwt-comet-Servers ist somit beendet. Es kann nun zur Implementierung des Clients übergegangen werden.

\subsection{Client}
\subsubsection{Installation}
Für die Installation des Clients genügen Eclipse und Eclipse-GWT-Plugin. Um mit der Implementierung des Clients beginnen zu können, muss in Eclipse eine neue "`Google Web Application'' kreiert werden. Eine detaillierte Beschreibung zur Initialisierung einer "`Google Web Application'' befindet sich im Kapitel \ref{streamhub-client-installation}. Es sind keine weiteren Installationen notwendig um mit der Implementierung beginnen zu können.

\subsubsection{Konfiguration}
Es sind keine Clientkonfigurationen notwendig.

\subsubsection{Implementierung}
Eine GWT-Applikation besitzt stets einen Einstiegspunkt. In unserem Beispiel ist der Einstiegspunkt die Methode \textit{onModuleLoad()} in der Klasse \textit{Comet\_gwt} (Einstiegspunkt wurde dank der Option "`Generate GWT project sample code'' bei der Erstellung der Eclipse-Projektes erstellt). Folgender Code befindet sich in der Klasse \textit{Comet\_gwt}:

\begin{lstlisting}[label=gwt-comet-client-impl,caption=Implementation des Client-Codes]
package ch.eiafr.tic.client;

import net.zschech.gwt.comet.client.CometClient;
import net.zschech.gwt.comet.client.CometSerializer;
import net.zschech.gwt.comet.client.SerialTypes;
import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.core.client.GWT;
import com.google.gwt.user.client.rpc.AsyncCallback;

/**
 * Entry point classes define <code>onModuleLoad()</code>.
 */
public class Comet_gwt implements EntryPoint {
  
  @SerialTypes({Message.class})
  public static abstract class MessageCometSerializer extends CometSerializer {
  }

  /**
   * This is the entry point method.
   */
  public void onModuleLoad() {
    MessagingServiceAsync messagingService;
    messagingService = GWT.create(MessagingService.class);
    messagingService.login(
        ""+System.currentTimeMillis(), 
        new AsyncCallback<Void>() {
      
      @Override
      public void onSuccess(Void result) {
        loggedOn();
      }
      
      @Override
      public void onFailure(Throwable caught) {
        MainView.getInstance().log("Login failed");
      }
    });
  }
  
  /**
   * Creates an instance of CometClient and starts it
   */
  private void loggedOn() {
    MainView.getInstance();
    CometSerializer serializer = GWT.create(MessageCometSerializer.class);
    CometClient client = new CometClient(
        GWT.getModuleBaseURL() + "comet", 
        serializer, 
        new MessageListener());
    client.start();
  }
 }
\end{lstlisting}

Wie im Kapitel \ref{design} ("`Prototyp Design'') beschrieben, muss sich der Client zuerst beim Server registrieren, damit er vom Server Nachrichten erhalten. Im obenstehenden Listing ist zu sehen, dass dazu das Interface mit der Beschreibung der öffentlichen Server-Methoden geladen werden muss. Sobald das Interface geladen ist, kann \textit{messagingService.login(...)} ausgeführt werden. Nachdem der Client beim Server registriert ist, wird die Methode \textit{loggedOn()} aufgerufen, welche eine Instanz von \textit{CometClient} kreiert. Bei Initialisierung von \textit{CometClient} muss der Pfad des \textit{url-pattern} des \textit{CometServlet}, welcher in der Datei \textit{web.xml} definiert wurde, als Parameter mitgegeben werden. Als zweiter Parameter wird eine Serialisierungsklasse mitgegeben (\textit{serializer}). Diese bestimmt welche Objekte serialisierbar sind. In unserem Beispiel wird ausschliesslich der Objek-Typ \textit{Message} serialisiert.

\begin{lstlisting}[label=gwt-comet-serialisierungsklasse,caption=Definierung der Serialisierungsklasse.]
@SerialTypes({Message.class})
public static abstract class MessageCometSerializer extends CometSerializer {
}
\end{lstlisting}

\begin{lstlisting}[label=gwt-comet-blah,caption=Instantiierung der Serialisierungsklasse]
CometSerializer serializer = GWT.create(MessageCometSerializer.class);
\end{lstlisting}

\begin{lstlisting}[label=gwt-comet-blah,caption=Instantiierung und Starten des Comet-Clients]
CometClient client = new CometClient(
        GWT.getModuleBaseURL() + "comet", 
        serializer, 
        new MessageListener());
client.start();
\end{lstlisting}

Der letzte Parameter ist der Listener, welcher für den Empfang der Server-Nachrichten verantwortlich ist.

In der Klasse \textit{MainView}, wo die Grafischen Elemente des Clients definiert sind, wird ein Knopf erstellt. Sobald auf den Knopf gedrückt wird, wird die Methode \textit{onMouseDown()} der Klasse \textit{OnTimerButtonClickedHandler} aufgerufen. In dieser Methode wird \textit{toggleTimer()} auf Serverseite aufgerufen.

\begin{lstlisting}[label=onmouse-down-gwt-comet,caption=Sobald der Benutzer auf den Client-Knopf drückt wird die Methode \textit{onMouseDown()} ausgeführt]
@Override
  public void onMouseDown(MouseDownEvent event) {
    MessagingServiceAsync messagingService;
    long clientTime = System.currentTimeMillis();
    Message message = new Message("timerButtonClicked", clientTime);
    messagingService = GWT.create(MessagingService.class);
    MainView mainView = MainView.getInstance();
    mainView.log("Client clicked Timer button at: " + clientTime);
    messagingService.toggleTimer(message, new MessageHandler());
  }
\end{lstlisting}

Nach Aufruf von \textit{messagingService.toggleTimer(message, new MessageHandler())}, führt der Server die bereits besprochenen Operationen durch. Sobald der Client eine Nachricht erhält, wird die Methode \textit{onMessage()} der Klasse \textit{MessageListener} ausgeführt.

\begin{lstlisting}[label=gwt-comet-client-onmessage,caption=Client nimmt mit dieser Methode Nachrichten vom Server entgegen und führt die entsprechenden Operationen aus]
public void onMessage(List<? extends Serializable> messages) {
    long clientTime = System.currentTimeMillis();
    MainView mainView = MainView.getInstance();
    
    for (Serializable message : messages) {
      if (message instanceof Message) { (*@\label{instance-of}@*)
        long serverTime = ((Message) message).getTime();
        long timeDifference = clientTime - serverTime;
        mainView.log("Time difference: " + timeDifference);
        mainView.log("Client received message from server at: " + clientTime);
        mainView.log("-----");
        if (((Message) message).isToggleButtonText()) {
          mainView.setTimerButtonText(((Message) message).getMessage());
        }
      } else {
        mainView.log("unknown type of message received");
      }
    }
  }
\end{lstlisting}


Im Kapitel \ref{design} wurde erklärt, dass nur ein einziger Listener auf Seite des Clients für den Empfang von Server-Nachrichten registriert wird. Deshalb muss nun in der Methode \textit{onMessage()} getestet werden, von welchem Typ die Server-Nachricht ist. Dies geschieht auf Zeile \ref{instance-of} von Listing \ref{gwt-comet-client-onmessage}. Falls die Nachricht vom Typ \textit{Message} ist, wird Zeitunterschied zwischen Server-Zeit und Client-Zeit im Browser ausgegeben. Wenn das Attribut \textit{isToggleButtonText} der Nachricht auf \textit{true} gesetzt ist, wird ebenfalls der Knopftext dementsprechend geändert.

Die Client-Implementierung ist somit abgeschlossen.